﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure.Storage;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Tax;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Events;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Security;
using Nop.Services.Shipping;
using Nop.Services.Tax;
using StackExchange.Profiling;

namespace Nop.Services.Logistics
{
    public class OrderProcessingService : IOrderProcessingService
    {
        #region Fields

        private readonly LogisticsInformationSettings _logisticsInformationSettings;
        private readonly IMeasureService _measureService;
        private readonly MeasureSettings _measureSettings;

        private readonly IWorkContext _workContext;
        private readonly IOrderService _orderService;
        private readonly IPermissionService _permissionService;
        private readonly ILocalizationService _localizationService;
        private readonly IWebHelper _webHelper;
        private readonly IProductAttributeParser _productAttributeParser;
        private readonly IProductService _productService;
        private readonly IProductAttributeService _productAttributeService;
        private readonly IShipmentService _shipmentService;
        private readonly IAddressService _addressService;
        private readonly ILogisticsService _logisticsSerivce;
        private readonly IGenericAttributeService _genericAttributeService;
        private readonly IWorkflowMessageService _workflowMessageService;
        private readonly IEventPublisher _eventPublisher;
        private readonly ICustomerActivityService _customerActivityService;
        private readonly IGiftCardService _giftCardService;
        private readonly ICustomerService _customerService;
        private readonly IDateTimeHelper _dateTimeHelper;
        private readonly ITaxService _taxService;

        private readonly ILogger _logger;
        #endregion

        #region Ctor

        public OrderProcessingService(
            LogisticsInformationSettings logisticsInformationSettings,
            MeasureSettings measureSettings,
            IMeasureService measureService,
            IWorkContext workContext,
            IOrderService orderService,
            IPermissionService permissionService,
            ILocalizationService localizationService,
            IWebHelper webHelper,
            IProductAttributeParser productAttributeParser,
            IProductService productService,
            IProductAttributeService productAttributeService,
            IShipmentService shipmentService,
            IAddressService addressService,
            ILogisticsService logisticsSerivce,
            IGenericAttributeService genericAttributeService,
            IWorkflowMessageService workflowMessageService,
            IEventPublisher eventPublisher,
            ICustomerActivityService customerActivityService,
            IGiftCardService giftCardService,
            ICustomerService customerService,
            IDateTimeHelper dateTimeHelper,
            ITaxService taxService,
            ILogger logger)
        {
            this._logisticsInformationSettings = logisticsInformationSettings;
            this._measureService = measureService;
            this._measureSettings = measureSettings;
            this._workContext = workContext;
            this._orderService = orderService;
            this._permissionService = permissionService;
            this._localizationService = localizationService;
            this._webHelper = webHelper;
            this._productAttributeParser = productAttributeParser;
            this._productService = productService;
            this._productAttributeService = productAttributeService;
            this._shipmentService = shipmentService;
            this._addressService = addressService;
            this._logisticsSerivce = logisticsSerivce;
            this._genericAttributeService = genericAttributeService;
            this._workflowMessageService = workflowMessageService;
            this._eventPublisher = eventPublisher;
            this._customerActivityService = customerActivityService;
            this._giftCardService = giftCardService;
            this._customerService = customerService;
            this._dateTimeHelper = dateTimeHelper;
            this._taxService = taxService;
            this._logger = logger;

            InitCharging(logisticsInformationSettings);
        }

        #endregion

        #region Permission

        public bool IsMembershipExpired(Customer customer)
        {
            var expireDate = customer.GetAttribute<DateTime?>(
                SystemCustomerAttributeNames.LogisticsMemberExpireDateTimeOnUtc,
                _genericAttributeService);

            if (!expireDate.HasValue)
            {
                return true;
            }

            if (expireDate <= DateTime.UtcNow)
            {
                _genericAttributeService.SaveAttribute<DateTime?>(customer, SystemCustomerAttributeNames.LogisticsMemberExpireDateTimeOnUtc, null);
                return true;
            }
            return false;
        }

        public bool IsPaymentExempt(Customer customer)
        {
            return _permissionService.Authorize(StandardPermissionProvider.LogisticsPaymentExempt, customer);
        }

        public ChargeRule GetChargeRule(Customer customer)
        {
            if (IsPaymentExempt(customer))
            {
                if (_permissionService.Authorize(StandardPermissionProvider.LogisticsVolumeShipping))
                {
                    return ChargeRule.Volume;
                }
                else
                {
                    return ChargeRule.Membership;
                }
            }
            return ChargeRule.PayPerUse;
        }

        public ProductVariant GetProdcutVariant(Customer customer)
        {
            bool isMember = true;
            int pvId = isMember ? _logisticsInformationSettings.PackageForMemberProductVarianId
                                : _logisticsInformationSettings.PackageNonMemberProductVarianId;
            var productVariant = _productService.GetProductVariantById(pvId);
            if (productVariant == null)
            {
                throw new NopException("Logisitics virtual product cannot be found {0}, member {1}", pvId, isMember);
            }
            return productVariant;
        }

        #endregion

        #region Charging

        private static object initChargingLocker = new object();

        private static void InitCharging(LogisticsInformationSettings settings)
        {
            lock (initChargingLocker)
            {
                if (AddtionalChargeHelper.Handlers == null)
                {
                    AddtionalChargeHelper.Handlers = new Dictionary<AddtionalAttributes, IAddtionalChargeHandler>();

                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.VerticalHandle, new FlatCharge(settings.VerticalHandleCharge));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.Fragile, new FlatCharge(settings.FragileCharge));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.DangerousGood, new FlatCharge(settings.DangerousGoodCharge));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.NightDeliver, new FlatCharge(settings.NightDeliverCharge));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.SatDeliver, new FlatCharge(settings.SatDeliverCharge));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.InterDistrict, new FlatCharge(settings.InterDistrictCharge));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.Insurance, new InsuranceCharge(settings.InsuranceChargePercentage, settings.MaxInsuranceValue, settings.InsuranceChargeStep));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.Residential, new FlatCharge(settings.ResidentialCharge));
                    AddtionalChargeHelper.Handlers.Add(AddtionalAttributes.Return, new FlatCharge(settings.ReturnCharge));
                }
            }
        }

        public Order NewOrder(Customer customer, Charge charge, int languageId, int? boundOrderId, int shippingAddressId, int billingAddressId, bool isRescheduled = false, bool isModifyPackageOrder = false, bool requiredPaid = true, bool isSimgleAndHaveGifCard = false)
        {
            var shippingAddress = _addressService.GetAddressById(shippingAddressId);
            var billingAddress = _addressService.GetAddressById(billingAddressId);
            if (shippingAddress == null)
            {
                throw new NopException("User address ({0}) cannot be found.", shippingAddressId);
            }
            if (billingAddress == null)
            {
                throw new NopException("Address ({0}) cannot be found.", billingAddressId);
            }

            //if has authority and no need to paid before 
            //var payStatus = IsPaymentExempt(customer) && !requiredPaid
            //    ? Core.Domain.Payments.PaymentStatus.Pending
            //    : Nop.Core.Domain.Payments.PaymentStatus.Paid;

            var payStatus = PaymentStatus.Pending;

            if (isSimgleAndHaveGifCard)
            {
                payStatus = PaymentStatus.Paid;
            }

            var order = new Order()
            {
                OrderGuid = Guid.NewGuid(),
                Customer = customer,
                CustomerLanguageId = languageId,
                CustomerTaxDisplayType = Nop.Core.Domain.Tax.TaxDisplayType.IncludingTax,
                CustomerIp = _webHelper.GetCurrentIpAddress(),
                OrderSubtotalInclTax = decimal.Zero,
                OrderSubtotalExclTax = charge.SubTotalExclTax,
                OrderSubTotalDiscountInclTax = decimal.Zero,
                OrderSubTotalDiscountExclTax = decimal.Zero,
                OrderShippingInclTax = decimal.Zero,
                OrderShippingExclTax = charge.FuelSurchargeExclTax,
                PaymentMethodAdditionalFeeInclTax = decimal.Zero,
                PaymentMethodAdditionalFeeExclTax = decimal.Zero,
                TaxRates = _logisticsInformationSettings.TaxRate.ToString(),
                OrderTax = charge.Tax,
                OrderTotal = charge.Total,
                RefundedAmount = decimal.Zero,
                OrderDiscount = decimal.Zero,
                CheckoutAttributeDescription = string.Empty,
                CheckoutAttributesXml = string.Empty,
                CustomerCurrencyCode = "CAD",
                CurrencyRate = 1M,
                AffiliateId = null,
                OrderStatus = OrderStatus.Processing,
                AllowStoringCreditCardNumber = false,
                CardType = string.Empty,
                CardName = string.Empty,
                CardNumber = string.Empty,
                MaskedCreditCardNumber = string.Empty,
                CardCvv2 = string.Empty,
                CardExpirationMonth = string.Empty,
                CardExpirationYear = string.Empty,
                PaymentMethodSystemName = string.Empty,
                AuthorizationTransactionId = string.Empty,
                AuthorizationTransactionCode = string.Empty,
                AuthorizationTransactionResult = string.Empty,
                CaptureTransactionId = string.Empty,
                CaptureTransactionResult = string.Empty,
                SubscriptionTransactionId = string.Empty,
                PurchaseOrderNumber = string.Empty,
                PaymentStatus = payStatus,
                PaidDateUtc = null,
                BillingAddress = billingAddress,
                ShippingAddress = shippingAddress,
                ShippingStatus = isRescheduled ? ShippingStatus.ShippingNotRequired : ShippingStatus.NotYetReady,
                ShippingMethod = "SpotsHUB EXPRESS",
                ShippingRateComputationMethodSystemName = "Shipping.Spotshub",
                VatNumber = string.Empty,
                CreatedOnUtc = DateTime.UtcNow,
                OrderKind = isModifyPackageOrder ? OrderKind.ExpressModify : OrderKind.Express,
                BoundOrderId = boundOrderId
            };

            return order;
        }

        public Charge CaculateTotal(OrderInformation orderInfo, Customer customer, out List<GiftCard> appliedGiftCards)
        {
            // TODO: Enable mutiple locations
            var li = orderInfo.Locations.Single();
            return CaculateTotal(li, customer, out appliedGiftCards, orderInfo.IsCommerce ?? false);
        }

        public Charge CaculateTotal(OrderInformation orderInfo, Customer customer)
        {
            List<GiftCard> appliedGiftCards = null;
            return CaculateTotal(orderInfo, customer, out appliedGiftCards);
        }

        public Charge CaculateTotal(LocationInformation locationInfo, Customer customer, out List<GiftCard> appliedGiftCards, bool isCommerce)
        {
            var charge = new Charge();
            int count = locationInfo.Packages.Count;
            appliedGiftCards = null;
            if (count > 0)
            {
                if (customer.CustomerRoleMappings.Any(cr => cr.CustomerRole.SystemName.Equals(SystemCustomerRoleNames.MemberShipWithFixedRate)))
                {
                    //For every package fixed 4.99 15% GST
                    for (int i = 0; i < count; i++)
                    {
                        charge.PackageCharges.Add(_logisticsInformationSettings.MemberShipWithFixedRateCharge);
                        charge.AddtionalCharges.Add(0);
                    }
                }
                else
                {
                    charge = CaculateTotal(customer, false, isCommerce, locationInfo.Packages.ToArray());
                }

                appliedGiftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer)
                    .Where(gc => gc.GiftCardType == GiftCardType.ExpressPackage)
                    .ToList();

                if (charge.Warnings.Count == 0)
                {
                    charge.SubTotalExclTax += charge.PackageCharges.Sum() + charge.AddtionalCharges.Sum();
                    charge.SubTotalInclDiscount = charge.SubTotalExclTax;
                    if (appliedGiftCards.Count > 0)
                    {
                        charge.SubTotalInclDiscount = charge.SubTotalInclDiscount - charge.PackageCharges[0] - charge.AddtionalCharges[0];
                    }
                    charge.FuelSurchargeExclTax = charge.SubTotalInclDiscount * _logisticsInformationSettings.FuelSurchargeRate;
                    charge.Tax = (charge.SubTotalInclDiscount + charge.FuelSurchargeExclTax) * _logisticsInformationSettings.TaxRate;
                }
            }

            return charge;
        }

        public Charge CaculateTotal(Customer customer, bool caculateSum, bool isCommerce, params PackageInformation[] packages)
        {
            var charge = new Charge();

            if (packages == null || packages.Count() == 0)
                return charge;

            ChargeRule chargeRule = ChargeRule.PayPerUse;
            if (customer.IsRegistered())
            {
                chargeRule = GetChargeRule(customer);
            }

            if (isCommerce && _permissionService.Authorize(StandardPermissionProvider.ManageStore))
            {
                chargeRule = ChargeRule.StoreManager;
            }

            PackageChargeHelper.GetCharge(charge, chargeRule, packages.ToList());
            if (charge.Warnings.Count == 0 && caculateSum)
            {
                charge.SubTotalExclTax += charge.PackageCharges.Sum() + charge.AddtionalCharges.Sum();
                charge.FuelSurchargeExclTax = charge.SubTotalExclTax * _logisticsInformationSettings.FuelSurchargeRate;
                charge.Tax = (charge.SubTotalExclTax + charge.FuelSurchargeExclTax) * _logisticsInformationSettings.TaxRate;
            }
            return charge;
        }

        public Charge CaculateTotal(LocationInformation locationInfo, Customer customer)
        {
            List<GiftCard> appliedGiftCards = null;
            return CaculateTotal(locationInfo, customer, out appliedGiftCards, false);
        }

        #endregion

        #region Measure

        public List<PackageInformation> GetConvertedPackages(IEnumerable<PackageInformation> packages)
        {
            return packages.Select(p => GetConvertedPackage(p))
                .ToList();
        }

        public PackageInformation GetConvertedPackage(PackageInformation package)
        {
            if (!package.IsLettersize && !package.IsCompactSize && !package.IsFullSize)
            {
                var pkg = new PackageInformation()
                {
                    Length = ConvertToPrimaryMeasureDimension(package.Length, package.DimensionMeasureId),
                    Width = ConvertToPrimaryMeasureDimension(package.Width, package.DimensionMeasureId),
                    Height = ConvertToPrimaryMeasureDimension(package.Height, package.DimensionMeasureId),
                    Weight = ConvertToPrimaryMeasureWeight(package.Weight, package.WeightMeasureId)
                };
                pkg.AddtionalAttributes = package.AddtionalAttributes;
                pkg.InsuranceValue = package.InsuranceValue;
                pkg.PackagePreset = package.PackagePreset;
                pkg.DimensionMeasureId = _measureSettings.BaseDimensionId;
                pkg.WeightMeasureId = _measureSettings.BaseWeightId;

                return pkg;
            }

            return package;
        }

        private string GetDimensionUnit(int? dimensionId)
        {
            return _measureService.GetMeasureDimensionById(dimensionId ?? _measureSettings.BaseDimensionId).Name;
        }

        private decimal? ConvertToPrimaryMeasureDimension(decimal? quantity, int? sourceMeasureDimensionId)
        {
            if (quantity == null || sourceMeasureDimensionId == null)
            {
                return null;
            }
            var source = _measureService.GetMeasureDimensionById(sourceMeasureDimensionId ?? _measureSettings.BaseDimensionId);
            return _measureService.ConvertToPrimaryMeasureDimension(quantity.Value, source);
        }

        private string GetWeightUnit(int? weightId)
        {
            return _measureService.GetMeasureWeightById(weightId ?? _measureSettings.BaseWeightId).Name;
        }

        private decimal? ConvertToPrimaryMeasureWeight(decimal? quantity, int? sourceMeasureWeightId)
        {
            if (quantity == null || sourceMeasureWeightId == null)
            {
                return null;
            }
            var source = _measureService.GetMeasureWeightById(sourceMeasureWeightId ?? _measureSettings.BaseWeightId);
            return _measureService.ConvertToPrimaryMeasureWeight(quantity.Value, source);
        }

        #endregion

        #region Formatter

        public string GetPackageInfoString(PackageInformation packageInfo)
        {
            StringBuilder sb = new StringBuilder();

            if (packageInfo.IsLettersize)
            {
                sb.Append(_localizationService.GetResource("Logistics.StandardPackage"));
            }
            else if (packageInfo.IsCompactSize)
            {
                sb.Append(_localizationService.GetResource("Logistics.Order.Package.CompactSize"));
            }
            else if (packageInfo.IsFullSize)
            {
                sb.Append(_localizationService.GetResource("Logistics.Order.Package.FullSize "));
            }
            else
            {
                if (packageInfo.Length.HasValue && packageInfo.Width.HasValue && packageInfo.Height.HasValue)
                {
                    sb.AppendFormat("{0:F2} {3} X {1:F2} {3} X {2:F2} {3}",
                        packageInfo.Length.Value,
                        packageInfo.Width.Value,
                        packageInfo.Height.Value,
                        GetDimensionUnit(packageInfo.DimensionMeasureId));
                }

                if (packageInfo.Weight.HasValue)
                {
                    if (sb.Length > 0)
                        sb.Append(" ");
                    sb.AppendFormat("W: {0:F2} {1}", packageInfo.Weight.Value, GetWeightUnit(packageInfo.WeightMeasureId));
                }
            }

            AddtionalAttributes attr = packageInfo.AddtionalAttributes;

            if (attr != AddtionalAttributes.None)
            {
                sb.Append(" ");
                sb.Append(attr.GetLocalizedEnum(_localizationService, _workContext));
            }

            if (packageInfo.InsuranceValue.HasValue)
            {
                sb.Append(" ");
                sb.AppendFormat(_localizationService.GetResource("Logistics.Order.InsuranceFormat"), packageInfo.InsuranceValue.Value);
            }

            return sb.ToString();
        }

        public string GetPackageAttributeDescription(PackageInformation packageInfo)
        {
            return GetPackageInfoString(packageInfo);
        }

        public string GetPackageAttributeXml(
            PackageInformation pi,
            IList<ProductVariantAttribute> productVariantAttributes,
            int customerId,
            int addressId)
        {
            string attributes = String.Empty;
            int id = 0;
            ProductVariantAttribute pva = null;
            if (pi.AddtionalAttributes.HasFlag(AddtionalAttributes.Fragile))
            {
                pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "IsFragile");
                if (pva != null)
                {
                    id = pva.ProductVariantAttributeValues.SingleOrDefault(x => x.Name == "True").Id;
                    attributes = _productAttributeParser.AddProductAttribute(attributes, pva, id.ToString());
                }
            }

            if (pi.AddtionalAttributes.HasFlag(AddtionalAttributes.DangerousGood))
            {
                pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "IsDangerousGood");
                if (pva != null)
                {
                    id = pva.ProductVariantAttributeValues.SingleOrDefault(x => x.Name == "True").Id;
                    attributes = _productAttributeParser.AddProductAttribute(attributes, pva, id.ToString());
                }
            }

            if (pi.AddtionalAttributes.HasFlag(AddtionalAttributes.VerticalHandle))
            {
                pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "IsVerticalHandling");
                if (pva != null)
                {
                    id = pva.ProductVariantAttributeValues.SingleOrDefault(x => x.Name == "True").Id;
                    attributes = _productAttributeParser.AddProductAttribute(attributes, pva, id.ToString());
                }
            }

            if (pi.AddtionalAttributes.HasFlag(AddtionalAttributes.NoLabel))
            {
                pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "IsNoLabel");
                if (pva != null)
                {
                    id = pva.ProductVariantAttributeValues.SingleOrDefault(x => x.Name == "True").Id;
                    attributes = _productAttributeParser.AddProductAttribute(attributes, pva, id.ToString());
                }
            }

            pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "Weight");
            if (pva != null)
                attributes = _productAttributeParser.AddProductAttribute(attributes, pva, pi.Weight.ToString());

            pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "Length");
            if (pva != null)
                attributes = _productAttributeParser.AddProductAttribute(attributes, pva, pi.Length.ToString());

            pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "Width");
            if (pva != null)
                attributes = _productAttributeParser.AddProductAttribute(attributes, pva, pi.Width.ToString());

            pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "Height");
            if (pva != null)
                attributes = _productAttributeParser.AddProductAttribute(attributes, pva, pi.Height.ToString());

            pva = productVariantAttributes.SingleOrDefault(a => a.ProductAttribute.Name == "Attributes");
            if (pva != null)
                attributes = _productAttributeParser.AddProductAttribute(attributes, pva, String.Format("{0}-!@-{1}", pi.Comment ?? "N/A", pi.AddtionalAttributes.ToString()));

            return attributes;
        }

        #endregion

        #region Order

        private string LoadOrderInformationStr(Customer customer)
        {
            if (customer == null)
            {
                customer = _workContext.CurrentCustomer;
            }

            return customer.GetAttribute<string>(SystemCustomerAttributeNames.LogisticsOrderInformation);
        }

        public void SaveOrderInformation(OrderInformation oi, Customer customer)
        {
            if (oi == null)
            {
                throw new NopException("Order information is empty");
            }

            if (oi.Locations != null)
            {
                if (oi.Locations.Count > 1)
                {
                    throw new NopException("Only supports single location.");
                }

                if (oi.Locations.Count == 1)
                {
                    var li = oi.Locations.SingleOrDefault();
                    if (!li.Packages.All(x => x.IsLettersize || x.IsCompactSize || x.IsFullSize || x.PackagePreset.HasValue || (x.Length.HasValue && x.Width.HasValue && x.Height.HasValue && x.Weight.HasValue)))
                    {
                        throw new NopException("Package dimension is invalid.");
                    }
                }
            }

            _genericAttributeService.SaveAttribute<OrderInformation>(customer, SystemCustomerAttributeNames.LogisticsOrderInformation, oi);
        }

        public OrderInformation LoadOrderInformation(Customer customer)
        {
            return customer.GetAttribute<OrderInformation>(SystemCustomerAttributeNames.LogisticsOrderInformation);
        }

        public void RemoveCurrentOrder(Customer customer, bool removeGiftCards = false)
        {
            _genericAttributeService.SaveAttribute<string>(customer, SystemCustomerAttributeNames.LogisticsOrderInformation, null);
            if (removeGiftCards)
            {
                _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer)
                    .ToList()
                    .ForEach(gc => customer.RemoveGiftCardCouponCode(gc.GiftCardCouponCode));
                _customerService.UpdateCustomer(customer);
            }
        }

        public bool HasPendingOrders(Customer customer)
        {
            try
            {
                var oi = LoadOrderInformation(customer);
                if (oi == null) return false;

                if (oi.CurrentLocationId == 0 || oi.Locations.Count == 0)
                {
                    RemoveCurrentOrder(customer);
                    return false;
                }
                return (!oi.OrderPaid);
            }
            catch (Exception)
            {
                RemoveCurrentOrder(customer);
                return false;
            }
        }

        private void MarkStoreOrderAttributes(OrderInformation oi, int boundOrderId)
        {
            var order = _orderService.GetOrderById(boundOrderId);
            if (order != null && order.OrderTotal <= _logisticsInformationSettings.StoreOrderLowRateAmount)
            {
                oi.Locations
                    .SelectMany(l => l.Packages)
                    .ToList()
                    .ForEach(pi => pi.AddtionalAttributes |= AddtionalAttributes.StoreOrderLowCharge);
            }
        }

        public int PlaceOrder(OrderInformation oi, Customer customer, int? boundOrderId, bool createShipments = true, bool paymentRequired = true, bool isLogisticOrderForFree = false)
        {
            if (_logisticsInformationSettings.TaxCategoryId == 0)
            {
                throw new NopException("Tax category must be set first.");
            }

            var taxRates = _taxService.GetTaxRate(_logisticsInformationSettings.TaxCategoryId, customer);
            if ((taxRates == null || taxRates.Count == 0) && !_taxService.IsTaxExempt(null, customer))
            {
                throw new NopException("Tax rates must be set for tax category {0}.", _logisticsInformationSettings.TaxCategoryId);
            }

            int orderId = -1;

            List<GiftCard> appliedGiftCards = null;

            if (oi.IsCommerce == true && boundOrderId.HasValue)
            {
                MarkStoreOrderAttributes(oi, boundOrderId.Value);
            }

            var charge = CaculateTotal(oi, customer, out appliedGiftCards);
            int languageId = _workContext.WorkingLanguage.Id;

            var profiler = MiniProfiler.Current;

            Order order;
            if (!oi.UserAddressId.HasValue)
            {
                throw new NopException("UserAddressId is empty.");
            }
            var userAddress = _addressService.GetAddressById(oi.UserAddressId.Value);
            if (userAddress == null)
            {
                throw new NopException("User address ({0}) cannot be found.", oi.UserAddressId.Value);
            }

            var li = oi.Locations.Single();
            var addr = _addressService.GetAddressById(li.AddressId);
            if (addr == null)
            {
                throw new NopException("Address ({0}) cannot be found.", li.AddressId);
            }

            if (customer.ShippingAddress == null)
            {
                customer.ShippingAddress = userAddress;
                _customerService.UpdateCustomer(customer);
            }
            if (customer.BillingAddress == null)
            {
                customer.BillingAddress = addr;
                _customerService.UpdateCustomer(customer);
            }


            if (oi.OrderId.HasValue && oi.OrderId > 0)
            {
                using (profiler.Step("Place Order: Load Order."))
                {
                    order = _orderService.GetOrderById(oi.OrderId.Value);
                    order.Customer = customer;
                    order.ShippingAddress = _addressService.GetAddressById(customer.ShippingAddress.Id);
                    order.BillingAddress = _addressService.GetAddressById(customer.BillingAddress.Id);
                    order.CustomerLanguageId = languageId;
                    order.OrderTax = charge.Tax;
                    order.OrderTotal = charge.Total;
                    order.OrderShippingExclTax = charge.FuelSurchargeExclTax;
                    order.OrderSubtotalExclTax = charge.SubTotalExclTax;
                    if (boundOrderId.HasValue)
                        order.BoundOrderId = boundOrderId;
                }
            }
            else
            {
                using (profiler.Step("Place Order: Create Order."))
                {
                    var isSimgleAndHaveGifCard = false;
                    //Order with single pkg. paid by gift card the Payment Status should be Paid
                    if (appliedGiftCards.Count > 0 && oi.Locations.FirstOrDefault().Packages.Count == 1)
                    {
                        isSimgleAndHaveGifCard = true;
                    }
                    order = NewOrder(customer, charge, languageId, boundOrderId, customer.ShippingAddress.Id, customer.BillingAddress.Id, false, false, paymentRequired, isSimgleAndHaveGifCard);
                    order.StoreId = 23;
                    //logistic order orderToal set 0
                    if (isLogisticOrderForFree)
                    {
                        order.OrderTotal = 0;
                        order.OrderSubtotalExclTax = 0;
                        order.OrderSubtotalInclTax = 0;
                        order.OrderSubTotalDiscountExclTax = 0;
                        order.OrderSubTotalDiscountInclTax = 0;
                        order.OrderTax = 0;
                        order.OrderShippingExclTax = 0;
                        order.OrderShippingInclTax = 0;
                    }

                    _orderService.InsertOrder(order);

                    order.OrderNotes.Add(new OrderNote()
                    {
                        Note = "Express order placed",
                        DisplayToCustomer = false,
                        CreatedOnUtc = DateTime.UtcNow
                    });
                    _customerActivityService.InsertActivity("Logistics.PlaceOrder", "Place an express order {0}", customer, order.Id);
                }
            }
            orderId = order.Id;
            oi.OrderId = order.Id;
            oi.OrderKindId = oi.OrderKindId > 0 ? oi.OrderKindId : (int)order.OrderKindId;
            //if (order.OrderKindId != null) oi.OrderKindId = (int)order.OrderKindId;

            if (createShipments)
            {
                var pvId = _logisticsInformationSettings.PackageForMemberProductVarianId;
                var productVariant = GetProdcutVariant(customer);
                var avaliableAttributes = _productAttributeService.GetProductVariantAttributesByProductVariantId(productVariant.Id);

                //Save customized order by info
                if (!String.IsNullOrWhiteSpace(li.OrderInfo))
                {
                    order.OrderByInfo = li.OrderInfo;
                }
                else
                {
                    order.OrderByInfo = null;
                }

                if (appliedGiftCards != null && appliedGiftCards.Count() > 0)
                {
                    var agc = appliedGiftCards.FirstOrDefault();
                    var gcuh = new GiftCardUsageHistory()
                    {
                        GiftCard = agc,
                        UsedWithOrder = order,
                        UsedValue = 1M,
                        CreatedOnUtc = DateTime.UtcNow
                    };
                    agc.GiftCardUsageHistory.Add(gcuh);
                    _giftCardService.UpdateGiftCard(agc);

                    var pkg = li.Packages.First();
                    pkg.InsuranceValue = null;
                    pkg.AddtionalAttributes &= ~AddtionalAttributes.Insurance;
                }

                Address from, to;
                if (li.IsUserAddressPickup)
                {
                    from = userAddress;
                    to = addr;
                }
                else
                {
                    to = userAddress;
                    from = addr;
                }

                DateTime? readyTime, pickupEndTime;
                GetPickuptime(li, from, out readyTime, out pickupEndTime);
                if (!li.PickupDate.HasValue || !li.PickupTimeWindowId.HasValue)
                {
                    // No scheduled date, packages are ready for pickup
                    order.ShippingStatus = ShippingStatus.ReadyForPickUp;
                }
                else if (readyTime.HasValue)
                {
                    order.ShippingStatus = ShippingStatus.NotYetReady;
                }

                //save pickup date to order 
                if (li != null && li.PickupDate.HasValue && li.PickupTimeWindowId.HasValue)
                {
                    order.ShipmentDate = li.PickupDate.Value;
                    order.ShippingTimeWindowsId = li.PickupTimeWindowId.Value;
                }
                else if (boundOrderId.HasValue)
                {
                    var oldOrder = _orderService.GetOrderById(boundOrderId.Value);
                    if (oldOrder != null)
                    {
                        order.ShipmentDate = oldOrder.ShipmentDate;
                        order.ShippingTimeWindowsId = oldOrder.ShippingTimeWindowsId;
                    }
                }

                if (!boundOrderId.HasValue && _logisticsSerivce.IsEarliestShippingDate(order))
                {
                    order.ShippingStatusId = (int)ShippingStatus.ReadyForPickUp;
                }

                _orderService.UpdateOrder(order);

                var taxSurchargeList = new List<Surcharge>();
                for (int i = 0; i < li.Packages.Count; i++)
                {
                    using (profiler.Step("Place Order: Add product variant and create shipment: " + i))
                    {
                        #region Add Product Variant

                        var pi = li.Packages[i];
                        string attributesXml = GetPackageAttributeXml(pi, avaliableAttributes, customer.Id, li.AddressId);

                        var opv = new OrderProductVariant()
                        {
                            OrderProductVariantGuid = Guid.NewGuid(),
                            Order = order,
                            ProductVariantId = pvId,
                            UnitPriceInclTax = decimal.Zero,
                            UnitPriceExclTax = decimal.Zero,
                            PriceInclTax = decimal.Zero,
                            PriceExclTax = charge.PackageCharges[i] + charge.AddtionalCharges[i],
                            AttributeDescription = GetPackageAttributeDescription(pi),
                            AttributesXml = attributesXml,
                            Quantity = 1,
                            DiscountAmountInclTax = decimal.Zero,
                            DiscountAmountExclTax = decimal.Zero,
                            DownloadCount = 0,
                            IsDownloadActivated = false,
                            LicenseDownloadId = 0,
                            ItemWeight = pi.Weight
                        };

                        order.OrderProductVariants.Add(opv);

                        _orderService.UpdateOrder(order);

                        #endregion

                        #region Create Shipment


                        var shipment = new Shipment()
                          {
                              Order = order,
                              ShippedDateUtc = null,
                              DeliveryDateUtc = null,
                              CreatedOnUtc = DateTime.UtcNow,
                              TrackingNumber = "Pending",
                              TotalWeight = null,
                              Length = null,
                              Width = null,
                              Height = null,
                              Attributes = null,
                              Comment = null,
                          };

                        if (!String.IsNullOrWhiteSpace(li.AddtionalInstruction))
                        {
                            shipment.Comment = li.AddtionalInstruction;
                        }

                        if (!String.IsNullOrWhiteSpace(pi.Comment))
                        {
                            if (shipment.Comment != null)
                            {
                                shipment.Comment += String.Format(", {0}", pi.Comment);
                            }
                            else
                            {
                                shipment.Comment = pi.Comment;
                            }
                        }

                        shipment.ShipmentOrderProductVariants.Add(
                            new ShipmentOrderProductVariant()
                            {
                                OrderProductVariantId = opv.Id,
                                Quantity = 1,
                            });

                        if (pi.Weight.HasValue)
                        {
                            shipment.TotalWeight = ConvertToPrimaryMeasureWeight(pi.Weight, pi.WeightMeasureId);
                        }

                        if (pi.Length.HasValue && pi.Width.HasValue && pi.Height.HasValue)
                        {
                            shipment.Length = ConvertToPrimaryMeasureDimension(pi.Length, pi.DimensionMeasureId);
                            shipment.Width = ConvertToPrimaryMeasureDimension(pi.Width, pi.DimensionMeasureId);
                            shipment.Height = ConvertToPrimaryMeasureDimension(pi.Height, pi.DimensionMeasureId);
                        }

                        shipment.Attributes = (int)pi.AddtionalAttributes;
                        shipment.InsuranceValue = pi.InsuranceValue;
                        if (pi.PackagePreset.HasValue)
                        {
                            shipment.SpotshubPackageSize = pi.PackagePreset.Value;
                            shipment.TotalWeight = 66;
                            switch (pi.PackagePreset.Value)
                            {
                                case (int)PackagePreset.Letter:
                                    shipment.Length = 12;
                                    shipment.Width = 9;
                                    shipment.Height = 1;
                                    break;
                                case (int)PackagePreset.Small:
                                    shipment.Length = 9;
                                    shipment.Width = 9;
                                    shipment.Height = 9;
                                    break;
                                case (int)PackagePreset.Medium:
                                    shipment.Length = 15;
                                    shipment.Width = 15;
                                    shipment.Height = 15;
                                    break;
                                case (int)PackagePreset.Large:
                                    shipment.Length = 22;
                                    shipment.Width = 22;
                                    shipment.Height = 22;
                                    break;
                                case (int)PackagePreset.ExtraLarge:
                                    shipment.Length = 29;
                                    shipment.Width = 229;
                                    shipment.Height = 9;
                                    break;
                                case (int)PackagePreset.None:
                                    if (shipment.Length == null)
                                        shipment.Length = 1;
                                    if (shipment.Width == null)
                                        shipment.Width = 1;
                                    if (shipment.Height == null)
                                        shipment.Height = 1;
                                    break;
                            }
                        }

                        _shipmentService.InsertShipment(shipment);

                        pi.ShipmentId = shipment.Id;

                        if (!from.Type.HasValue && !from.VerifiedType.HasValue)
                        {
                            from.Type = AddressType.Residential;
                            from.TypeId = (int)AddressType.Residential;
                            if (order.BoundOrderId.HasValue)
                            {
                                var boundOrder = _orderService.GetOrderById(order.BoundOrderId.Value);
                                if (boundOrder != null && boundOrder.OrderKind == OrderKind.Commerce)
                                {
                                    from.Type = AddressType.Business;
                                    from.TypeId = (int)AddressType.Business;
                                    _addressService.UpdateAddress(from);
                                }
                            }
                        }

                        using (profiler.Step("Place Order: CreateShipment"))
                        {
                            pi.PackageId = _logisticsSerivce.CreateShipment(customer.Id, from, to, null, shipment, oi.OrderKindId, readyTime, pickupEndTime);
                            pi.TrackingNumber = shipment.TrackingNumber;
                        }

                        //add surcharge -- only pay gst, need fuel surcharge fee
                        opv.PriceExclTax = opv.PriceExclTax * (1 + _logisticsInformationSettings.FuelSurchargeRate);
                        taxRates.ForEach(t => t.Value = opv.PriceExclTax * t.PercentageOrFee / 100);
                        taxSurchargeList.AddRange(taxRates.ToSurcharges(null, opv));
                        #endregion
                    }
                }

                //add surcharge -- only pay gst
                _taxService.AddSurchargesToOrder(order, taxSurchargeList);

                var district = _logisticsSerivce.GetDistrictByZipPostalCode(userAddress.ZipPostalCode);
                if (district != null)
                {
                    order.DistrictId = district.Id;
                }
                else
                {
                    order.OrderNotes.Add(new OrderNote()
                    {
                        Note = string.Format("The order district cannot be determined"),
                        DisplayToCustomer = false,
                        CreatedOnUtc = DateTime.UtcNow
                    });
                }

                #region Notifications & notes

                foreach (var pk in li.Packages)
                {
                    if (pk.PackageId.HasValue)
                    {
                        _logisticsSerivce.CreatePackageNote(
                                pk.PackageId.Value,
                                PackageNoteType.SystemEvent,
                                String.Format(_logisticsSerivce.GetPackageNoteContentByTemplate(PackageNoteStandardTemplates.OrderCreated,
                                        _workContext.WorkingLanguage.Id), order.Id),
                                _workContext.CurrentCustomer.Id,
                                false,
                                _logisticsSerivce.GetPackageById(pk.PackageId.Value).Status,
                                _addressService.GetAddressById(li.AddressId)
                                );
                    }
                }
                //send email notifications
                //int orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendLogisticsOrderPlacedManagerNotification(order, languageId);
                //if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0)
                //{
                //    order.OrderNotes.Add(new OrderNote()
                //    {
                //        Note = string.Format("\"Order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId),
                //        DisplayToCustomer = false,
                //        CreatedOnUtc = DateTime.UtcNow
                //    });
                //}

                //_logger.InsertLog(Core.Domain.Logging.LogLevel.Information,
                //   "邮件：OrderProcessingService 中 调用SendLogisticsOrderPlacedCustomerNotification插入邮件方法", string.Format("orderID:{0},orderPackageCount:{1}", order.Id,li.Packages.Count));

                //string keyword = "LogisticsOrderPlaced.CustomerNotification";
                //int orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendLogisticsOrderPlacedCustomerNotification(order, order.CustomerLanguageId, keyword);
                //if (orderPlacedCustomerNotificationQueuedEmailId > 0)
                //{
                //    order.OrderNotes.Add(new OrderNote()
                //    {
                //        Note = string.Format("\"Order placed\" email (to customer) has been queued. Queued email identifier: {0}.", orderPlacedCustomerNotificationQueuedEmailId),
                //        DisplayToCustomer = false,
                //        CreatedOnUtc = DateTime.UtcNow
                //    });
                //}
                #endregion
            }

            _orderService.UpdateOrder(order);
            SaveOrderInformation(oi, customer);

            return orderId;
        }

        public int PlaceOrderWithoutShipment(OrderInformation oi, Customer customer, Charge charge, int packageId, int boundOrderId, bool isMailingReceiver = false, bool isModifyPackageOrder = false)
        {
            if (charge == null)
                throw new ArgumentNullException("charge");
            if (boundOrderId == 0)
            {
                throw new NopException("Rescheduled order must have bound order.");
            }

            if (_logisticsInformationSettings.TaxCategoryId == 0)
            {
                throw new NopException("Tax category must be set first.");
            }

            var taxRates = _taxService.GetTaxRate(_logisticsInformationSettings.TaxCategoryId, customer);
            if (taxRates == null || taxRates.Count == 0)
            {
                throw new NopException("Tax rates must be set for tax category {0}.", _logisticsInformationSettings.TaxCategoryId);
            }

            int orderId = -1;
            int languageId = _workContext.WorkingLanguage.Id;
            var profiler = MiniProfiler.Current;
            var li = oi.Locations.Single();
            Order order;

            if (!oi.UserAddressId.HasValue)
            {
                throw new NopException("UserAddressId is empty.");
            }
            var userAddress = _addressService.GetAddressById(oi.UserAddressId.Value);
            if (userAddress == null)
            {
                throw new NopException("User address ({0}) cannot be found.", oi.UserAddressId.Value);
            }
            var addr = _addressService.GetAddressById(li.AddressId);
            if (addr == null)
            {
                throw new NopException("Address ({0}) cannot be found.", li.AddressId);
            }
            var package = _logisticsSerivce.GetPackageById(packageId);
            if (package == null)
                throw new ArgumentNullException("package");

            using (profiler.Step("Place Order: Create Order."))
            {
                #region Create Order



                //get order
                order = NewOrder(customer, charge, languageId, boundOrderId, userAddress.Id, addr.Id, true, isModifyPackageOrder);

                _orderService.InsertOrder(order);
                orderId = order.Id;
                #endregion
            }

            //Save customized order by info
            if (!String.IsNullOrWhiteSpace(li.OrderInfo))
            {
                order.OrderByInfo = li.OrderInfo;
            }
            else
            {
                order.OrderByInfo = null;
            }

            var taxSurchargeList = new List<Surcharge>();
            var pvId = _logisticsInformationSettings.PackageModificationChargeProductVarianId;
            using (profiler.Step("Place Order: Add product variant and create shipment: "))
            {
                #region Add Product Variant

                var opv = new OrderProductVariant()
                {
                    OrderProductVariantGuid = Guid.NewGuid(),
                    Order = order,
                    ProductVariantId = pvId,
                    UnitPriceInclTax = decimal.Zero,
                    UnitPriceExclTax = decimal.Zero,
                    PriceInclTax = decimal.Zero,
                    PriceExclTax = charge.SubTotalExclTax,
                    Quantity = 1,
                    DiscountAmountInclTax = decimal.Zero,
                    DiscountAmountExclTax = decimal.Zero,
                    DownloadCount = 0,
                    IsDownloadActivated = false,
                    LicenseDownloadId = 0,
                };
                order.OrderProductVariants.Add(opv);
                _orderService.UpdateOrder(order);

                //add surcharge
                taxRates.ForEach(t => t.Value = charge.Tax);
                taxSurchargeList.AddRange(taxRates.ToSurcharges(null, opv));
                #endregion
            }

            //add surcharge -- only pay gst
            _taxService.AddSurchargesToOrder(order, taxSurchargeList);
            // Update the packageIDs in the order information.
            SaveOrderInformation(oi, customer);

            var district = _logisticsSerivce.GetDistrictByZipPostalCode(userAddress.ZipPostalCode);
            if (district != null)
            {
                order.DistrictId = district.Id;
            }
            else
            {
                order.OrderNotes.Add(new OrderNote()
                {
                    Note = string.Format("The order district cannot be determined"),
                    DisplayToCustomer = false,
                    CreatedOnUtc = DateTime.UtcNow
                });
            }

            _orderService.UpdateOrder(order);

            #region Notifications & notes
            _logisticsSerivce.CreatePackageNote(
                    package.Id,
                    PackageNoteType.SystemEvent,
                    String.Format(_logisticsSerivce.GetPackageNoteContentByTemplate(PackageNoteStandardTemplates.OrderCreated,
                                    _workContext.WorkingLanguage.Id), order.Id),
                    _workContext.CurrentCustomer.Id,
                    true,
                    package.Status,
                    _addressService.GetAddressById(li.AddressId)
                    );

            //notes, messages-- may need to change content
            order.OrderNotes.Add(new OrderNote()
            {
                Note = "Express rescheduled service order placed",
                DisplayToCustomer = false,
                CreatedOnUtc = DateTime.UtcNow
            });

            //send email notifications-- may need to change content
            int orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendLogisticsOrderPlacedManagerNotification(order, languageId);
            if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0)
            {
                order.OrderNotes.Add(new OrderNote()
                {
                    Note = string.Format("\"Express rescheduled service order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId),
                    DisplayToCustomer = false,
                    CreatedOnUtc = DateTime.UtcNow
                });
            }

            string keyword = null;
            if (package.DeliveryRescheduleCount > 0)
            {
                keyword = "RescheduleDeliveryDate.CustomerNotification";
            }
            else if (package.DeliveryAddressChangeCount > 0)
            {
                keyword = "ChangeDeliveryAddress.CustomerNotification";
            }
            if (!String.IsNullOrEmpty(keyword))
            {
                /*
                //send email to sender
                int rescheduleOrderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendLogisticsOrderPlacedCustomerNotification(order, order.CustomerLanguageId, keyword);
                if (rescheduleOrderPlacedCustomerNotificationQueuedEmailId > 0)
                {
                    order.OrderNotes.Add(new OrderNote()
                    {
                        Note = string.Format("\"Express rescheduled service order placed\" email (to customer) has been queued. Queued email identifier: {0}.", rescheduleOrderPlacedCustomerNotificationQueuedEmailId),
                        DisplayToCustomer = false,
                        CreatedOnUtc = DateTime.UtcNow
                    });
                }

                //send email to receiver-- may need to change content
                if (isMailingReceiver)
                {
                    string email = customer.BillingAddress.Email;
                    string name = customer.BillingAddress.GetCompanyNameOrFullName();
                    int rescheduleOrderForReceiverPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendLogisticsRescheduleOrderPlacedReceiverNotification(order, order.CustomerLanguageId, email, name, keyword);
                    if (rescheduleOrderForReceiverPlacedCustomerNotificationQueuedEmailId > 0)
                    {
                        order.OrderNotes.Add(new OrderNote()
                        {
                            Note = string.Format("\"Express rescheduled service order placed\" email (to receiver) has been queued. Queued email identifier: {0}.", rescheduleOrderForReceiverPlacedCustomerNotificationQueuedEmailId),
                            DisplayToCustomer = false,
                            CreatedOnUtc = DateTime.UtcNow
                        });
                    }
                }
                */
            }

            _orderService.UpdateOrder(order);
            //-- may need to change content
            _customerActivityService.InsertActivity("Logistics.PlaceOrder", "Place an express rescheduled service order# {0}", customer, order.Id);
            #endregion

            return orderId;
        }

        public void GetPickuptime(LocationInformation li, Address pickupAddress, out DateTime? readyTime, out DateTime? pickupEndTime)
        {
            readyTime = null;
            pickupEndTime = null;
            try
            {
                if (li == null || li.PickupTimeWindowId == null || li.PickupDate == null || pickupAddress == null)
                {
                    return;
                }


                var timeWindow = _logisticsSerivce.GetShippingTimeWindowById(li.PickupTimeWindowId.Value);
                readyTime = new DateTime(
                        li.PickupDate.Value.Year,
                        li.PickupDate.Value.Month,
                        li.PickupDate.Value.Day,
                        timeWindow.NoEarlyThan,
                        0, 0, DateTimeKind.Unspecified);

                pickupEndTime = new DateTime(
                        li.PickupDate.Value.Year,
                        li.PickupDate.Value.Month,
                        li.PickupDate.Value.Day,
                        timeWindow.NoLateThan,
                        0, 0, DateTimeKind.Unspecified);

                if (pickupAddress == null)
                {
                    return;
                }

                District district = null;
                district = _logisticsSerivce.GetDistrictByZipPostalCode(pickupAddress.ZipPostalCode);

                string timeZoneId = district == null ? _workContext.CurrentCustomer.TimeZoneId : district.TimeZoneId;
                if (timeZoneId != null)
                {
                    var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);

                    readyTime = _dateTimeHelper.ConvertToUtcTime(readyTime.Value, timeZoneInfo);

                    pickupEndTime = _dateTimeHelper.ConvertToUtcTime(pickupEndTime.Value, timeZoneInfo);
                }
            }
            catch (Exception exc)
            {
                throw new NopException("Cannot parse the pickup time from pickup time {0}, address {1}, Exception {2}", li.PickupTimeWindowId, pickupAddress == null ? 0 : pickupAddress.Id, exc);
            }
        }

        public string GetValidatedPickupTime(LocationInformation li, OrderKind orderKind, string zipPostalCode, AddressType type, out string warning)
        {
            warning = "";
            string pickupTime = "";
            var validDays = _logisticsSerivce.GetValidateDays((int)orderKind, type, zipPostalCode);
            var firstAvailablePickupTime = validDays.First().Time.Date.ToString() + " " + validDays.First().ShipmentTimeSpans.First().Description;
            if (li.PickupDate.HasValue && li.PickupTimeWindowId.HasValue)
            {
                var foundDate = validDays.FirstOrDefault(d => d.Time.Date == li.PickupDate.Value.Date && d.ShipmentTimeSpans.FirstOrDefault(st => st.Id == li.PickupTimeWindowId) != null);
                if (foundDate != null)
                {
                    pickupTime = foundDate.Time.Date.ToString() + " " + foundDate.ShipmentTimeSpans.FirstOrDefault(st => st.Id == li.PickupTimeWindowId.Value).Description;
                    return pickupTime;
                }
                else
                {
                    warning = "Your scheduled time is no longer available. It is changed to the next available time.";

                }
            }
            return firstAvailablePickupTime;
        }

        public void GetPickupTime(LocationInformation li, Address pickupAddress, Order order)
        {
            if (li == null || li.PickupDate == null || li.PickupTimeWindowId == null || li.PickupTimeWindowId <= 0)
            {
                //get early validate date
                var address = order.ShippingAddress;
                var dayInfo = GetEarliestPickupTime((OrderKind)order.OrderKindId.Value, pickupAddress.ZipPostalCode, pickupAddress.VerifiedType.HasValue ? pickupAddress.VerifiedType.Value : pickupAddress.Type.Value);

                order.ShipmentDate = dayInfo.Time;
                order.ShippingTimeWindowsId = dayInfo.ShipmentTimeSpans.FirstOrDefault().Id;
            }
            else
            {
                order.ShipmentDate = (DateTime)li.PickupDate;
                order.ShippingTimeWindowsId = (int)li.PickupTimeWindowId;
            }
        }

        private Day GetEarliestPickupTime(OrderKind orderKindId, string zipPostalCode, AddressType type)
        {
            var days = _logisticsSerivce.GetValidateDays((int)orderKindId, type, zipPostalCode);

            if (days == null || days.Count <= 0)
                throw new Exception(string.Format("days is null,params error:orderKindId{0},zipPostalCode:{1},AddressType{2} ", orderKindId, zipPostalCode, type));

            return days.FirstOrDefault();

        }

        /// <summary>
        /// send placed email to customer and store ower
        /// </summary>
        /// <param name="orderId"></param>
        public void SendLogisticPlacedNotification(int orderId)
        {
            if (orderId <= 0)
            {
                _logger.InsertLog(Core.Domain.Logging.LogLevel.Error, "SendLogisticPlacedNotification error", string.Format("order null"));
                return;
            }

            var order = _orderService.GetOrderById(orderId);
            if (order == null)
            {
                _logger.InsertLog(Core.Domain.Logging.LogLevel.Error, "SendLogisticPlacedNotification error", string.Format("orderid:{0},order null", orderId));
                return;
            }

            //send email notifications
            int orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendLogisticsOrderPlacedManagerNotification(order, order.CustomerLanguageId);
            if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0)
            {
                order.OrderNotes.Add(new OrderNote()
                {
                    Note = string.Format("\"Order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId),
                    DisplayToCustomer = false,
                    CreatedOnUtc = DateTime.UtcNow
                });
            }

            _logger.InsertLog(Core.Domain.Logging.LogLevel.Information,
               "邮件：OrderProcessingService 中 调用SendLogisticsOrderPlacedCustomerNotification插入邮件方法", string.Format("orderID:{0},logistic controller", order.Id));

            const string keyword = "LogisticsOrderPlaced.CustomerNotification";
            var orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendLogisticsOrderPlacedCustomerNotification(order, order.CustomerLanguageId, keyword);
            if (orderPlacedCustomerNotificationQueuedEmailId > 0)
            {
                order.OrderNotes.Add(new OrderNote()
                {
                    Note = string.Format("\"Order placed\" email (to customer) has been queued. Queued email identifier: {0}.", orderPlacedCustomerNotificationQueuedEmailId),
                    DisplayToCustomer = false,
                    CreatedOnUtc = DateTime.UtcNow
                });
            }
            _orderService.UpdateOrder(order);
        }

        #endregion

        #region Payment Information

        public void SavePaymentInformation(ProcessPaymentRequest processPaymentRequest, Customer customer = null)
        {
            if (processPaymentRequest == null)
                throw new ArgumentNullException("processPaymentRequest");

            if (customer == null)
                customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId);

            if (customer == null)
                throw new ArgumentNullException("customer");

            _genericAttributeService.SaveAttribute<ProcessPaymentRequest>(customer, SystemCustomerAttributeNames.LogisticsPaymentInformaiton, processPaymentRequest);
        }

        public ProcessPaymentRequest LoadPaymentInformation(Customer customer)
        {
            try
            {
                return customer.GetAttribute<ProcessPaymentRequest>(SystemCustomerAttributeNames.LogisticsPaymentInformaiton);
            }
            catch (InvalidCastException)
            {
                // remove the payment info if the information is obsoleted
                _genericAttributeService.SaveAttribute<ProcessPaymentRequest>(customer, SystemCustomerAttributeNames.LogisticsPaymentInformaiton, null);
            }
            return null;
        }

        public void RemovePaymentInformation(Customer customer)
        {
            _genericAttributeService.SaveAttribute<ProcessPaymentRequest>(customer, SystemCustomerAttributeNames.LogisticsPaymentInformaiton, null);
        }

        #endregion
    }
}
